package com.flowerfat.wheellib;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Build.VERSION;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;

import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * Created by janus on 2017/7/6.
 * LoopView
 */

public class LoopView extends View {
    private float scaleX = 1.05F;
    Context context;
    Handler handler;
    private GestureDetector gestureDetector;
    OnItemSelectedListener onItemSelectedListener;
    ScheduledExecutorService mExecutor = Executors.newSingleThreadScheduledExecutor();
    private ScheduledFuture<?> mFuture;
    Paint paintOuterText;
    Paint paintCenterText;
    Paint paintIndicator;
    List<String> items;
    int textSize;
    int maxTextWidth;
    int maxTextHeight;
    int colorGray;
    int colorBlack;
    int colorLightGray;
    float lineSpacingMultiplier;
    boolean isLoop;
    int firstLineY;
    int secondLineY;
    int totalScrollY;
    int initPosition;
    private int selectedItem;
    int preCurrentIndex;
    int change;
    int itemsVisible;
    int measuredHeight;
    int measuredWidth;
    int paddingLeft = 0;
    int paddingRight = 0;
    int halfCircumference;
    int radius;
    private int mOffset = 0;
    private float previousY;
    long startTime = 0L;
    private Rect tempRect = new Rect();

    public LoopView(Context context) {
        super(context);
        initLoopView(context);
    }

    public LoopView(Context context, AttributeSet attributeset) {
        super(context, attributeset);
        initLoopView(context);
    }

    public LoopView(Context context, AttributeSet attributeset, int defStyleAttr) {
        super(context, attributeset, defStyleAttr);
        initLoopView(context);
    }

    private void initLoopView(Context context) {
        this.context = context;
        this.handler = new MessageHandler(this);
        this.gestureDetector = new GestureDetector(context, new LoopViewGestureListener(this));
        this.gestureDetector.setIsLongpressEnabled(false);
        this.lineSpacingMultiplier = 2.0F;
        this.isLoop = true;
        this.itemsVisible = 9;
        this.textSize = 0;
        this.colorGray = -5263441;
        this.colorBlack = -13553359;
        this.colorLightGray = -3815995;
        this.totalScrollY = 0;
        this.initPosition = -1;
        initPaints();
        setTextSize(16.0F);
    }

    private void initPaints() {
        paintOuterText = new Paint();
        paintOuterText.setColor(colorGray);
        paintOuterText.setAntiAlias(true);
        paintOuterText.setTypeface(Typeface.MONOSPACE);
        paintOuterText.setTextSize((float) textSize);
        paintCenterText = new Paint();
        paintCenterText.setColor(colorBlack);
        paintCenterText.setAntiAlias(true);
        paintCenterText.setTextScaleX(scaleX);
        paintCenterText.setTypeface(Typeface.MONOSPACE);
        paintCenterText.setTextSize((float) textSize);
        paintIndicator = new Paint();
        paintIndicator.setColor(colorLightGray);
        paintIndicator.setAntiAlias(true);
        if (VERSION.SDK_INT >= 11) {
            setLayerType(1, null);
        }

    }

    private void remeasure() {
        if (items != null) {
            measureTextWidthHeight();
            halfCircumference = (int) ((float) maxTextHeight * lineSpacingMultiplier * (float) (itemsVisible - 1));
            measuredHeight = (int) ((double) (halfCircumference * 2) / 3.141592653589793D);
            radius = (int) ((double) halfCircumference / 3.141592653589793D);
            measuredWidth = maxTextWidth + paddingLeft + paddingRight;
            firstLineY = (int) (((float) measuredHeight - lineSpacingMultiplier * (float) maxTextHeight) / 2.0F);
            secondLineY = (int) (((float) measuredHeight + lineSpacingMultiplier * (float) maxTextHeight) / 2.0F);
            if (initPosition == -1) {
                if (isLoop) {
                    initPosition = (items.size() + 1) / 2;
                } else {
                    initPosition = 0;
                }
            }

            preCurrentIndex = initPosition;
        }
    }

    private void measureTextWidthHeight() {
        for (int i = 0; i < items.size(); ++i) {
            String s1 = items.get(i);
            paintCenterText.getTextBounds(s1, 0, s1.length(), tempRect);
            int textWidth = tempRect.width();
            if (textWidth > maxTextWidth) {
                maxTextWidth = (int) ((float) textWidth * scaleX);
            }

            paintCenterText.getTextBounds("星期", 0, 2, tempRect);
            int textHeight = tempRect.height();
            if (textHeight > maxTextHeight) {
                maxTextHeight = textHeight;
            }
        }

    }

    void smoothScroll(LoopView.ACTION action) {
        cancelFuture();
        if (action == LoopView.ACTION.FLING || action == LoopView.ACTION.DAGGLE) {
            float itemHeight = lineSpacingMultiplier * (float) maxTextHeight;
            mOffset = (int) (((float) totalScrollY % itemHeight + itemHeight) % itemHeight);
            if ((float) mOffset > itemHeight / 2.0F) {
                mOffset = (int) (itemHeight - (float) mOffset);
            } else {
                mOffset = -mOffset;
            }
        }

        mFuture = mExecutor.scheduleWithFixedDelay(new SmoothScrollTimerTask(this, mOffset), 0L, 10L, TimeUnit.MILLISECONDS);
    }

    protected final void scrollBy(float velocityY) {
        cancelFuture();
        byte velocityFling = 10;
        mFuture = mExecutor.scheduleWithFixedDelay(new InertiaTimerTask(this, velocityY), 0L, (long) velocityFling, TimeUnit.MILLISECONDS);
    }

    public void cancelFuture() {
        if (mFuture != null && !mFuture.isCancelled()) {
            mFuture.cancel(true);
            mFuture = null;
        }

    }

    public final void setNotLoop() {
        isLoop = false;
    }

    public final void setLoop() {
        isLoop = true;
    }

    public final void setTextSize(float size) {
        if (size > 0.0F) {
            textSize = (int) (context.getResources().getDisplayMetrics().density * size);
            paintOuterText.setTextSize((float) textSize);
            paintCenterText.setTextSize((float) textSize);
        }

    }

    public final void setInitPosition(int initPosition) {
        this.initPosition = initPosition;
    }

    public final void setListener(OnItemSelectedListener OnItemSelectedListener) {
        onItemSelectedListener = OnItemSelectedListener;
    }

    public final void setItems(List<String> items) {
        this.items = items;
        remeasure();
        invalidate();
    }

    @Override
    public int getPaddingLeft() {
        return paddingLeft;
    }

    @Override
    public int getPaddingRight() {
        return paddingRight;
    }

    public void setViewPadding(int left, int top, int right, int bottom) {
        paddingLeft = left;
        paddingRight = right;
    }

    public final int getSelectedItem() {
        return selectedItem;
    }

    protected final void onItemSelected() {
        if (onItemSelectedListener != null) {
            postDelayed(new OnItemSelectedRunnable(this), 200L);
        }

    }

    @Override
    protected void onDraw(Canvas canvas) {
        if (items != null) {
            String[] as = new String[itemsVisible];
            change = (int) ((float) totalScrollY / (lineSpacingMultiplier * (float) maxTextHeight));
            preCurrentIndex = initPosition + change % items.size();
            if (!isLoop) {
                if (preCurrentIndex < 0) {
                    preCurrentIndex = 0;
                }

                if (preCurrentIndex > items.size() - 1) {
                    preCurrentIndex = items.size() - 1;
                }
            } else {
                if (preCurrentIndex < 0) {
                    preCurrentIndex += items.size();
                }

                if (preCurrentIndex > items.size() - 1) {
                    preCurrentIndex -= items.size();
                }
            }

            int j2 = (int) ((float) totalScrollY % (lineSpacingMultiplier * (float) maxTextHeight));

            int j1;
            for (int k1 = 0; k1 < itemsVisible; ++k1) {
                j1 = preCurrentIndex - (itemsVisible / 2 - k1);
                if (isLoop) {
                    if (j1 < 0) {
                        j1 += items.size();
                    }

                    if (j1 > items.size() - 1) {
                        j1 -= items.size();
                    }

                    as[k1] = items.get(j1);
                } else if (j1 < 0) {
                    as[k1] = "";
                } else if (j1 > items.size() - 1) {
                    as[k1] = "";
                } else {
                    as[k1] = items.get(j1);
                }
            }

            canvas.drawLine(0.0F, (float) firstLineY, (float) measuredWidth, (float) firstLineY, paintIndicator);
            canvas.drawLine(0.0F, (float) secondLineY, (float) measuredWidth, (float) secondLineY, paintIndicator);

            for (j1 = 0; j1 < itemsVisible; ++j1) {
                canvas.save();
                float itemHeight = (float) maxTextHeight * lineSpacingMultiplier;
                double radian = (double) (itemHeight * (float) j1 - (float) j2) * 3.141592653589793D / (double) halfCircumference;
                float angle = (float) (90.0D - radian / 3.141592653589793D * 180.0D);
                if (angle < 90.0F && angle > -90.0F) {
                    int translateY = (int) ((double) radius - Math.cos(radian) * (double) radius - Math.sin(radian) * (double) maxTextHeight / 2.0D);
                    canvas.translate(0.0F, (float) translateY);
                    canvas.scale(1.0F, (float) Math.sin(radian));
                    if (translateY <= firstLineY && maxTextHeight + translateY >= firstLineY) {
                        canvas.save();
                        canvas.clipRect(0, 0, measuredWidth, firstLineY - translateY);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintOuterText, tempRect), (float) maxTextHeight, paintOuterText);
                        canvas.restore();
                        canvas.save();
                        canvas.clipRect(0, firstLineY - translateY, measuredWidth, (int) itemHeight);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintCenterText, tempRect), (float) maxTextHeight, paintCenterText);
                        canvas.restore();
                    } else if (translateY <= secondLineY && maxTextHeight + translateY >= secondLineY) {
                        canvas.save();
                        canvas.clipRect(0, 0, measuredWidth, secondLineY - translateY);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintCenterText, tempRect), (float) maxTextHeight, paintCenterText);
                        canvas.restore();
                        canvas.save();
                        canvas.clipRect(0, secondLineY - translateY, measuredWidth, (int) itemHeight);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintOuterText, tempRect), (float) maxTextHeight, paintOuterText);
                        canvas.restore();
                    } else if (translateY >= firstLineY && maxTextHeight + translateY <= secondLineY) {
                        canvas.clipRect(0, 0, measuredWidth, (int) itemHeight);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintCenterText, tempRect), (float) maxTextHeight, paintCenterText);
                        selectedItem = items.indexOf(as[j1]);
                    } else {
                        canvas.clipRect(0, 0, measuredWidth, (int) itemHeight);
                        canvas.drawText(as[j1], (float) getTextX(as[j1], paintOuterText, tempRect), (float) maxTextHeight, paintOuterText);
                    }

                    canvas.restore();
                } else {
                    canvas.restore();
                }
            }

        }
    }

    private int getTextX(String a, Paint paint, Rect rect) {
        paint.getTextBounds(a, 0, a.length(), rect);
        int textWidth = rect.width();
        textWidth = (int) ((float) textWidth * scaleX);
        return (measuredWidth - textWidth) / 2;
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        remeasure();
        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean eventConsumed = gestureDetector.onTouchEvent(event);
        float itemHeight = lineSpacingMultiplier * (float) maxTextHeight;
        float y;
        switch (event.getAction()) {
            case 0:
                startTime = System.currentTimeMillis();
                cancelFuture();
                previousY = event.getRawY();
                break;
            case 1:
            default:
                if (!eventConsumed) {
                    y = event.getY();
                    double l1 = Math.acos((double) (((float) radius - y) / (float) radius)) * (double) radius;
                    int circlePosition = (int) ((l1 + (double) (itemHeight / 2.0F)) / (double) itemHeight);
                    float extraOffset = ((float) totalScrollY % itemHeight + itemHeight) % itemHeight;
                    mOffset = (int) ((float) (circlePosition - itemsVisible / 2) * itemHeight - extraOffset);
                    if (System.currentTimeMillis() - startTime > 120L) {
                        smoothScroll(LoopView.ACTION.DAGGLE);
                    } else {
                        smoothScroll(LoopView.ACTION.CLICK);
                    }
                }
                break;
            case 2:
                float dy = previousY - event.getRawY();
                previousY = event.getRawY();
                totalScrollY = (int) ((float) totalScrollY + dy);
                if (!isLoop) {
                    y = (float) (-initPosition) * itemHeight;
                    float l = (float) (items.size() - 1 - initPosition) * itemHeight;
                    if ((float) totalScrollY < y) {
                        totalScrollY = (int) y;
                    } else if ((float) totalScrollY > l) {
                        totalScrollY = (int) l;
                    }
                }
        }

        invalidate();
        return true;
    }

    public static enum ACTION {
        CLICK,
        FLING,
        DAGGLE;

        private ACTION() {
        }
    }
}

